using EmitDebugging;
using EmitDebugging.Tests.Types;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Reflection;

namespace EmitDebugging.Tests
{
	[TestClass]
	public sealed class TypeDescriptorTests : AssemblyCreationTests
	{
		[TestMethod]
		public void GetGenericTypeBuilderDescription()
		{
			using(var assembly = AssemblyCreationTests.CreateDebuggingAssembly("GenericTypeBuilderDescriptor"))
			{
				using(var type = AssemblyCreationTests.CreateDebuggingType(
					assembly, assembly.Builder.GetDynamicModule(assembly.Builder.GetName().Name), "Holder"))
				{
					type.Builder.DefineGenericParameters("T", "U", "V");

					var descriptor = new TypeDescriptor(type.Builder);
					Assert.AreEqual("class GenericTypeBuilderDescriptor.Holder`3<T, U, V>",
						descriptor.Value);
				}
			}		
		}

		[TestMethod]
		public void GetTypeBuilderDescription()
		{
			using(var assembly = AssemblyCreationTests.CreateDebuggingAssembly("TypeBuilderDescriptor"))
			{
				using(var type = AssemblyCreationTests.CreateDebuggingType(
					assembly, assembly.Builder.GetDynamicModule(assembly.Builder.GetName().Name), "Holder"))
				{
					var descriptor = new TypeDescriptor(type.Builder);
					Assert.AreEqual("class TypeBuilderDescriptor.Holder",
						descriptor.Value);
				}
			}		
		}

		[TestMethod]
		public void GetDerivedGenericTypeDescription()
		{
			var target = typeof(SimpleDerivedGenericType<Uri>);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleDerivedGenericType`1<class [System]System.Uri>",
				new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"EmitDebugging.Tests.Types.SimpleDerivedGenericType`1<class [System]System.Uri>",
				new TypeDescriptor(target, false).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleDerivedGenericType`1<class [System]System.Uri>&",
				new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleDerivedGenericType`1<class [System]System.Uri>[]",
				new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleDerivedGenericType`1<class [System]System.Uri>[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetExternalSimpleReferenceTypeScenarios()
		{
			var target = typeof(SimpleReferenceType);
			var external = typeof(TypeDescriptor);
			Assert.AreEqual(
				"class [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleReferenceType",
				new TypeDescriptor(target, external.Assembly).Value);
			Assert.AreEqual(
				"[EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleReferenceType",
				new TypeDescriptor(target, external.Assembly, false).Value);
			Assert.AreEqual(
				"class [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleReferenceType&",
				new TypeDescriptor(target.MakeByRefType(), external.Assembly).Value);
			Assert.AreEqual(
				"class [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleReferenceType[]",
				new TypeDescriptor(target.MakeArrayType(), external.Assembly).Value);
			Assert.AreEqual(
				"class [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleReferenceType[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType(), external.Assembly).Value);
		}

		[TestMethod]
		public void GetExternalValueTypeDescription()
		{
			var target = typeof(SimpleValueType);
			var external = typeof(TypeDescriptor);
			Assert.AreEqual(
				"valuetype [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleValueType",
				new TypeDescriptor(target, external.Assembly).Value);
			Assert.AreEqual(
				"[EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleValueType",
				new TypeDescriptor(target, external.Assembly, false).Value);
			Assert.AreEqual(
				"valuetype [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleValueType&",
				new TypeDescriptor(target.MakeByRefType(), external.Assembly).Value);
			Assert.AreEqual(
				"valuetype [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleValueType[]",
				new TypeDescriptor(target.MakeArrayType(), external.Assembly).Value);
			Assert.AreEqual(
				"valuetype [EmitDebugging.Tests]EmitDebugging.Tests.Types.SimpleValueType[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType(), external.Assembly).Value);
		}

		[TestMethod]
		public void GetFloat32Scenarios()
		{
			var target = typeof(float);
			Assert.AreEqual(
				"float32", new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"float32&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"float32[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"float32[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetFloat64Scenarios()
		{
			var target = typeof(double);
			Assert.AreEqual(
				"float64", new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"float64&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"float64[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"float64[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetGenericTypeDescription()
		{
			var target = typeof(SimpleGenericType<MethodInfo, Uri>);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<class [mscorlib]System.Reflection.MethodInfo, class [System]System.Uri>",
				new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"EmitDebugging.Tests.Types.SimpleGenericType`2<class [mscorlib]System.Reflection.MethodInfo, class [System]System.Uri>",
				new TypeDescriptor(target, false).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<class [mscorlib]System.Reflection.MethodInfo, class [System]System.Uri>&",
				new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<class [mscorlib]System.Reflection.MethodInfo, class [System]System.Uri>[]",
				new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<class [mscorlib]System.Reflection.MethodInfo, class [System]System.Uri>[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetOpenGenericTypeDescription()
		{
			var target = typeof(SimpleGenericType<,>);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<T, U>",
				new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"EmitDebugging.Tests.Types.SimpleGenericType`2<T, U>",
				new TypeDescriptor(target, false).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<T, U>&",
				new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<T, U>[]",
				new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleGenericType`2<T, U>[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetInt16Scenarios()
		{
			var target = typeof(short);
			Assert.AreEqual("int16", new TypeDescriptor(target).Value);
			Assert.AreEqual("int16&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("int16[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("int16[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetInt32Scenarios()
		{
			var target = typeof(int);
			Assert.AreEqual("int32", new TypeDescriptor(target).Value);
			Assert.AreEqual("int32&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("int32[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("int32[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetInt64Scenarios()
		{
			var target = typeof(long);
			Assert.AreEqual("int64", new TypeDescriptor(target).Value);
			Assert.AreEqual("int64&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("int64[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("int64[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetInt8Scenarios()
		{
			var target = typeof(sbyte);
			Assert.AreEqual("int8", new TypeDescriptor(target).Value);
			Assert.AreEqual("int8&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("int8[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("int8[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetObjectScenarios()
		{
			var target = typeof(object);
			Assert.AreEqual("object", new TypeDescriptor(target).Value);
			Assert.AreEqual("object&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("object[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("object[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetSimpleReferenceTypeScenarios()
		{
			var target = typeof(SimpleReferenceType);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleReferenceType",
				new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"EmitDebugging.Tests.Types.SimpleReferenceType",
				new TypeDescriptor(target, false).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleReferenceType&",
				new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleReferenceType[]",
				new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"class EmitDebugging.Tests.Types.SimpleReferenceType[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetSimpleValueTypeScenarios()
		{
			var target = typeof(SimpleValueType);
			Assert.AreEqual(
				"valuetype EmitDebugging.Tests.Types.SimpleValueType",
				new TypeDescriptor(target).Value);
			Assert.AreEqual(
				"EmitDebugging.Tests.Types.SimpleValueType",
				new TypeDescriptor(target, false).Value);
			Assert.AreEqual(
				"valuetype EmitDebugging.Tests.Types.SimpleValueType&",
				new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual(
				"valuetype EmitDebugging.Tests.Types.SimpleValueType[]",
				new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual(
				"valuetype EmitDebugging.Tests.Types.SimpleValueType[]&",
				new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetStringScenarios()
		{
			var target = typeof(string);
			Assert.AreEqual("string", new TypeDescriptor(target).Value);
			Assert.AreEqual("string&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("string[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("string[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetUInt16Scenarios()
		{
			var target = typeof(ushort);
			Assert.AreEqual("uint16", new TypeDescriptor(target).Value);
			Assert.AreEqual("uint16&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("uint16[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("uint16[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetUInt32Scenarios()
		{
			var target = typeof(uint);
			Assert.AreEqual("uint32", new TypeDescriptor(target).Value);
			Assert.AreEqual("uint32&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("uint32[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("uint32[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetUInt64Scenarios()
		{
			var target = typeof(ulong);
			Assert.AreEqual("uint64", new TypeDescriptor(target).Value);
			Assert.AreEqual("uint64&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("uint64[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("uint64[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetUInt8Scenarios()
		{
			var target = typeof(byte);
			Assert.AreEqual("uint8", new TypeDescriptor(target).Value);
			Assert.AreEqual("uint8&", new TypeDescriptor(target.MakeByRefType()).Value);
			Assert.AreEqual("uint8[]", new TypeDescriptor(target.MakeArrayType()).Value);
			Assert.AreEqual("uint8[]&", new TypeDescriptor(target.MakeArrayType().MakeByRefType()).Value);
		}

		[TestMethod]
		public void GetNullScenario()
		{
			Assert.AreEqual("void", new TypeDescriptor(null).Value);
		}

		[TestMethod]
		public void GetVoidScenarios()
		{
			Assert.AreEqual("void", new TypeDescriptor(typeof(void)).Value);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void PassNullAssembly()
		{
			new TypeDescriptor(typeof(long), (null as Assembly));
		}
	}
}
